﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using BIG = System.Numerics.BigInteger;
using System.Linq;
using System.Timers;

namespace PodpisyElektorniczne.Classes
{
    public class SchemaSetts : SchemaProperties
    {
        private static Timer Timer { get; set; }
        private static bool toStop { get; set; }

        public SchemaSetts(string st)
        {
            new SchemaProperties();
            Filepath =string.Format("{0}{1}{2}", @"SchemeSettings_",st ,".xml");
            toStop = false;
            Timer = new Timer(59000);
            Timer.Elapsed += new ElapsedEventHandler(onElapseTimerEvent);
            Timer.Start();
        }

        private static void onElapseTimerEvent(object sender, ElapsedEventArgs e)
        {
            toStop = true;
            Timer.Stop();
        }


        public SchemaSetts(SchemaProperties sps, string st)
        {
            OrderP = sps.OrderP;
            SubOrderQ = sps.SubOrderQ;
            GeneratorG = sps.GeneratorG;
            DashedGeneratorG = sps.DashedGeneratorG;
            SecretR = sps.SecretR;
            SecretRj = sps.SecretRj;

            if (Filepath == null)
                Filepath = string.Format("{0}{1}{2}",@"SchemeSettings_",st ,".xml");

            toStop = false;
            Timer = new Timer(59000);
            Timer.Elapsed += new ElapsedEventHandler(onElapseTimerEvent);
            Timer.Start();
        }

        //szukanie rzędów grupy i podgrupy, warunek jaki muszą spełniać jest hardkodowany:  q | p-1 => (p-1) % q == 0 
        public static BIG[] FindPrimeGroupOrders(int len)
        {
            var orderOfGroupZp = new BigInteger();
            var orderOfGroupZq = new BigInteger();
            var upperBorder = new BigInteger();
            var isPrime = false;
            var isDivisor = false;
            var divisors = new List<BigInteger>();
           
                orderOfGroupZp = BigInteger.genPseudoPrime(8*len, Settings.PrimeTrials,new Random());
                upperBorder = orderOfGroupZp-1;
                for (BigInteger i = 2; i < orderOfGroupZp; i++)
                {
                    if (i.isProbablePrime(Settings.PrimeTrials))
                    {
                        while (upperBorder % i == 0)
                        {
                            divisors.Add(i);
                            upperBorder = upperBorder/i;
                            if ((upperBorder.isProbablePrime(10) || divisors.Count >= 3) )
                            {
                                if(upperBorder.isProbablePrime(78) )divisors.Add(upperBorder);
                                return new[] { BIG.Parse(orderOfGroupZp.ToString()), BIG.Parse((divisors.ToArray())[divisors.Count - 1].ToString()) };
                            }
                        }
                        if((orderOfGroupZp-1) % i == 0 && i.ToString().Length >=4)
                            i = divisors.Aggregate((current, x) => current*x);
                    }
                }
            
            return new[]{BIG.Parse(orderOfGroupZp.ToString()), BIG.Parse(orderOfGroupZq.ToString()) };
        }
        

        public static BIG[] finder(int len)
        {
            var cspParam = new CspParameters { KeyNumber = (int)KeyNumber.Exchange };

            var rng = new RSACryptoServiceProvider(8 * len, cspParam);
            var rsaParam = rng.ExportParameters(true);
            
            var p = BIG.Abs(new BIG(rsaParam.P));
            var border = (BIG)BIG.Divide(p-1, 20);
            //border *= (BIG)double.MaxValue;
            var rem = new BIG(-1);
            var result = new List<BIG>();
            //BIG.DivRem(p - 1, border, out rem);
            var divident = p-1;
            for(BIG i = 0; i < p; i++)
            {
                if((new BigInteger(i.ToString(),10)).isProbablePrime(78))
                {
                    BIG.DivRem(divident, i, out rem);
                    while (rem == 0)
                    {
                        result.Add(i);
                        divident = BIG.Divide(divident, i);
                        BIG.DivRem(divident, i, out rem);
                        if((new BigInteger(divident.ToString(),10)).isProbablePrime(78))
                        {
                            result.Add(divident);
                            return new[] {p,divident};
                        }
                    }

                }
                //return default(BIG[]);
            }

            return new[] {p, border};
        }


        //szukanie generatorów grup: problem jest czy szukać generatora modulo Zq czy Zp <-- te wartości mam już dane 
        public static BIG FindSubGroupGenerator(BIG orderOfZp, BIG orderOfZq)
        {
            var generator = BIG.Parse("2");

            while (BIG.ModPow(generator, orderOfZq-1 , orderOfZq) != 1)// <-- co tutaj ma być?? mam szukać generatora modulo orderOfZp czy Zq ??
            {
                //Console.WriteLine(generator + @": " + zero);
                ++generator;
            }
            
            return BIG.Parse(generator.ToString());
        }

        //szukanie generatora grupy z daszkiem: szukam kolejnego generatora tej samej grupy
        public static BIG FindSubGroupGenerator(BIG orderOfZp, BIG orderOfZq, BIG gen)
        {
            var generator = gen;
            
            while (BIG.ModPow(generator, orderOfZq-1 , orderOfZq) != 1 || generator == gen) //<-- czy modulo Zq czy modulo Zp ? w jakiej grupie szukam generatora
            {
                if (generator >= orderOfZq) return 0;
                //Console.WriteLine(generator + @": " + zero);
                ++generator;
            }

            return BIG.Parse(generator.ToString());
        }

        public void GenerateSecrets()
        {
            try
            {
                var rng = new RNGCryptoServiceProvider();
                var rngByts = new byte[Form1.KeyPrefs.LengthOrderZp];//ta wartość brać z settings
                rng.GetBytes(rngByts);

                var r = new BIG(rngByts);

                rng = new RNGCryptoServiceProvider();
                rng.GetBytes(rngByts);
                var rj = new BIG(rngByts);

                SecretR = r < 0 ? r*(-1) : r ;
                SecretRj = rj < 0 ? rj * (-1) : rj;
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }
        
        public bool GenerateSchemaSettings()
        {
            var f = false;
            if (ToString() == string.Empty) throw new Exception("There is no Secrets. First generate secret.");
            try
            {
                while (!f)
                {
                    var neGens = FindPrimeGroupOrders(Form1.KeyPrefs.LengthOrderZq);
                    SubOrderQ = neGens[1]; 
                    OrderP = neGens[0]; 
                    GeneratorG = FindSubGroupGenerator(OrderP, SubOrderQ);
                    DashedGeneratorG = FindSubGroupGenerator(OrderP, SubOrderQ,GeneratorG);

                    if (OrderP != 0 && SubOrderQ != 0 && GeneratorG != 0 && DashedGeneratorG != 0)
                    {
                        XmlSerializeSchemaProperties();
                        f = true;
                    }
                    
                }
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
        
    }
}
